use reqwest::{Response, Result};
use serde::{Deserialize, Serialize};
use std::fs::{self, File};
use std::io::Write;
use std::io::{Error, ErrorKind};
use std::process::Command;
use std::{path, string};

// pub mod ffmpeg;
pub mod listener;

#[derive(Serialize, Deserialize)]
pub struct History {
    chunks: Vec<String>,
    path: String,
}

impl History {
    pub fn new(path: &str) -> Self {
        Self {
            chunks: Vec::new(),
            path: String::from(path),
        }
    }

    pub fn load(path: &str) -> Self {
        if let Ok(history_content) = fs::read_to_string(path) {
            if let Ok(temp) = serde_json::from_str::<Vec<String>>(history_content.as_str()) {
                return Self {
                    chunks: temp,
                    path: String::from(path),
                };
            }
        }
        return Self::new(path);
    }

    pub fn push(&mut self, chunk: &str) {
        self.chunks.push(chunk.to_string());
    }

    pub fn flush(&mut self) {
        if let Ok(history_content) = serde_json::to_string_pretty(&self.chunks) {
            fs::write(self.path.as_str(), history_content).expect("saving history failed.");
        }
    }

    pub fn iter(&self) -> std::slice::Iter<'_, std::string::String> {
        return self.chunks.iter();
    }
}

pub async fn fetch(url: &str) -> Result<Response> {
    return reqwest::get(url).await;
}
pub fn extract_base_url(url: &str) -> &str {
    let mut current: i32 = -1;
    let mut prefix = String::new();
    let mut last_char = 'h';
    for c in url.chars() {
        current += 1;
        if c == '/' {
            if last_char != '/' && !prefix.ends_with(":") {
                break;
            }
        }
        last_char = c;
        prefix.push(c);
    }
    if current > -1 {
        return url.get(0..current as usize).unwrap_or("");
    }
    return "";
}
pub fn extract_base_path_url(url: &str) -> &str {
    let mut current: i32 = 0;
    let mut last_index = -1;
    for c in url.chars() {
        if c == '/' {
            last_index = current;
        }
        current += 1;
    }
    if last_index > -1 {
        return url.get(0..last_index as usize).unwrap_or("");
    }
    return "";
}
pub fn extract_resource_name(url: &str) -> &str {
    let mut last_slash_pos = 0;
    let mut current = -1;
    let mut rpos = url.len();
    let finder = |c: char| {
        current += 1;
        if c == '?' || c == '#' {
            return true;
        } else if c == '/' {
            last_slash_pos = current + 1;
        }
        return false;
    };

    if let Some(pos) = url.find(finder) {
        rpos = pos;
    }
    let start_index = last_slash_pos as usize;
    let end_index = rpos;
    return url.get(start_index..end_index).unwrap_or("");
}
pub fn extract_field_value(field: &str) -> &str {
    if let Some(lpos) = field.find("\"") {
        if let Some(rpos) = field.rfind("\"") {
            let start_index = lpos;
            let end_index = rpos;
            println!("efv: {},{}", start_index, end_index);
            return field.get(start_index..end_index).unwrap_or("");
        }
    }
    return "";
}
pub fn extract_filename(content_diposition: &str) -> &str {
    for part in content_diposition.split(";") {
        if part.starts_with("filename") {
            let name = extract_field_value(part);
            if !name.is_empty() {
                return name;
            }
        } else if part.starts_with("name") {
            let name = extract_field_value(part);
            if !name.is_empty() {
                return name;
            }
        }
    }
    return "";
}
pub trait ProgressMonitor {
    fn on_start(&mut self, url: &str, source_name: &str, destination: &str) -> ();
    fn on_update(&mut self, downloaded: u64, total: u64) -> ();
    fn on_end(&mut self, source_name: &str, downloaded: u64, total: u64) -> std::io::Result<()>;
    fn is_downloaded(&self, source_name: &str) -> bool;
    fn on_downloaded(&mut self, source_name: &str, url: &str) -> ();
}

pub async fn write<F>(
    url: &str,
    path: &str,
    monitor: &mut F,
    history: &mut History,
) -> std::io::Result<()>
where
    F: ProgressMonitor,
{
    let mut resource_name = extract_resource_name(url);
    let mut filename = String::new();
    filename.push_str(path);
    filename.push(path::MAIN_SEPARATOR);
    filename.push_str(resource_name);
    if monitor.is_downloaded(resource_name) {
        if let Ok(metadata) = std::fs::metadata(filename.as_str()) {
            monitor.on_start(url, resource_name, filename.as_str());
            let download_size = metadata.len();
            let total_size = metadata.len();
            println!("skip {}", resource_name);
            return monitor.on_end(resource_name, download_size, total_size);
        }
    }

    if let Ok(mut response) = fetch(url).await {
        if monitor.is_downloaded(resource_name) {
            if let Ok(metadata) = std::fs::metadata(filename.as_str()) {
                monitor.on_start(url, resource_name, filename.as_str());
                let download_size = metadata.len();
                let total_size = metadata.len();
                println!("skip {}", resource_name);
                return monitor.on_end(resource_name, download_size, total_size);
            }
        }

        let mut download_size = 0;
        let total_size = response.content_length().unwrap_or(0);
        let mut file = File::create(filename.as_str())?;
        monitor.on_start(url, resource_name, filename.as_str());
        while let Ok(chunk) = response.chunk().await {
            if let Some(c) = chunk {
                download_size += c.len() as u64;
                file.write_all(&c)?;
                monitor.on_update(download_size, total_size);
            } else {
                break;
            }
        }
        file.sync_all()?;
        monitor.on_downloaded(resource_name, url);
        history.push(url);
        history.flush();
        return monitor.on_end(resource_name, download_size as u64, total_size);
    }
    Err(Error::new(
        ErrorKind::NotConnected,
        "can not get response from url",
    ))
}

pub fn ffmpeg_convert(source: &str, destination: &str) {
    Command::new("third-party/ffmpeg/bin/ffmpeg")
        .args(["-allowed_extensions", "ALL", "-i", source, destination])
        .status()
        .expect("failed to execute ffmpeg to convert video.");
}
